package com.zkh.myutils.utils;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;

import com.zkh.myutils.code.Charsets;
import com.zkh.myutils.io.file.FileUtils;
import com.zkh.myutils.io.log.Logger;

/**
 * 配置文件工具类，用于处理 键值对properties类型的配置文件
 * @author zkh
 */
public class ConfigureUtils {
	
	//默认节名
	public static String defaultSection = Utils.getUUID();
	
	/**
	 * 加载配置文件(properties文件)，返回Map配置信息，默认编码UTF-8
	 * @param configFile 配置文件名，项目中classes下的配置文件，可以包含子目录。<b>（不能以“/”开头）</b>
	 * @return
	 */
	public static Map<String, String> loadProperties(String configFile){
		return loadProperties(configFile, Charsets.UTF_8);
	}
	
	/**
	 * 加载配置文件(键值对properties类型的配置文件)，返回Map配置信息
	 * @param configFile 配置文件名，项目中classes下的配置文件，可以包含子目录。<b>（不能以“/”开头）</b>
	 * @param charset 编码，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @return
	 */
	public static Map<String, String> loadProperties(String configFile, Charset charset){
		try(InputStream is = getInputStream(configFile)){
			return loadProperties(is, charset);
		} catch (IOException e) {
			throw new RuntimeException("配置文件解析异常", e);
		}
	}
	
	/**
	 * 加载配置文件(键值对properties类型的配置文件)，返回Map配置信息，默认编码UTF-8
	 * @param inputStream 配置文件流
	 * @return
	 */
	public static Map<String, String> loadProperties(InputStream inputStream){
		return loadProperties(inputStream, Charsets.UTF_8);
	}
	
	/**
	 * 加载配置文件(键值对properties类型的配置文件)，返回Map配置信息
	 * @param inputStream 配置文件流
	 * @param charset 编码，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @return
	 */
	public static Map<String, String> loadProperties(InputStream inputStream, Charset charset){
		//按编码加载读取配置
		try(InputStreamReader isr = new InputStreamReader(inputStream, charset)) {
			//容器
			Map<String, String> ctn = new HashMap<String, String>();
			//实例化Properties对象
			Properties prop = new Properties();
			//读取配置
			prop.load(isr);
			//获取所有配置项
			Set<Entry<Object, Object>> set = prop.entrySet();
			//遍历
			for(Entry<Object, Object> kv : set){
				ctn.put(kv.getKey().toString(), kv.getValue().toString());
			}
			return ctn;
		} catch (Exception e) {
			throw new RuntimeException("配置文件解析异常", e);
		}
	}
	
	/**
	 * 保存配置(保存成键值对properties类型的配置文件)
	 * @param configFile 配置文件名，项目中classes下的配置文件，可以包含子目录。<b>（不能以“/”开头）</b>
	 * @param configs 配置，值为空时保存为空字符串
	 */
	public static void saveProperties(String configFile, Map<String, String> configs){
		//为空
		if(configs==null){
			return;
		}
		//配置文件绝对路径
		String filename = ConfigureUtils.class.getClassLoader().getResource(configFile).getFile();
		//路径转码
		try {filename = URLDecoder.decode(filename, "UTF-8");} catch (UnsupportedEncodingException e) {Logger.error(e);}
		//加载配置文件，创建输出流
		try(InputStream is = getInputStream(configFile);
			OutputStream os = new FileOutputStream(filename)) {
			//实例化Properties对象
			Properties prop = new Properties();
			//读取配置
			prop.load(is);
			//遍历Map配置
			Set<Entry<String, String>> kvs = configs.entrySet();
			//遍历Map配置
			for(Entry<String, String> kv: kvs){
				prop.put(kv.getKey(), kv.getValue()==null?"":kv.getValue());
			}
			//保存
			prop.store(os, null);
		} catch (IOException e) {
			Logger.error(e);
			throw new RuntimeException("配置文件保存失败");
		}
	}
	
	/**
	 * 加载配置文件(ini文件)，返回Map配置信息，默认编码UTF-8
	 * @param configFile 配置文件名，项目中classes下的配置文件，可以包含子目录。<b>（不能以“/”开头）</b>
	 * @return
	 */
	public static Map<String, Map<String, String>> loadInit(String configFile){
		return loadInit(configFile, Charsets.UTF_8);
	}
	
	/**
	 * 加载配置文件(ini文件)，返回Map配置信息
	 * @param configFile 配置文件名，项目中classes下的配置文件，可以包含子目录。<b>（不能以“/”开头）</b>
	 * @param charset 编码，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @return
	 */
	public static Map<String, Map<String, String>> loadInit(String configFile, Charset charset){
		try(InputStream is = getInputStream(configFile)){
			return loadInit(is, charset);
		} catch (IOException e) {
			throw new RuntimeException("配置文件解析异常", e);
		}
	}
	
	/**
	 * 加载配置文件(ini文件)，返回Map配置信息，默认编码UTF-8
	 * @param inputStream 配置文件流
	 * @return
	 */
	public static Map<String, Map<String, String>> loadInit(InputStream inputStream){
		return loadInit(inputStream, Charsets.UTF_8);
	}
	
	/**
	 * 加载配置文件(ini文件)，返回Map配置信息
	 * @param inputStream 配置文件流
	 * @param charset 编码，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @return
	 */
	public static Map<String, Map<String, String>> loadInit(InputStream inputStream, Charset charset){
		//配置容器
		Map<String, Map<String, String>> map = new HashMap<String, Map<String, String>>();
		//分节名
		String[] section = {defaultSection};
		//读取
		FileUtils.read(inputStream, charset, (line, content)->{
			//有数据，才解析值
			if(StringUtils.isNotEmpty(content)) {
				//检查分节名称
				if(content.matches("\\[.+\\]")) {
					section[0] = content.substring(1, content.length()-1);
				}else if(content.matches("\\w+=.*")) {
					//分隔键和值
					String[] kv = content.split("=");
					//是否存在key，不存在，就创建
					if(!map.containsKey(section[0])) {
						map.put(section[0], new HashMap<String, String>());
					}
					map.get(section[0]).put(kv[0], kv.length==2 ? kv[1] : "");
				}else if(!content.startsWith(";")){
					throw new IllegalArgumentException("配置格式错误，行" + line);
				}
			}
		});
		//返回数据容器
		return map;
	}
	
	/**
	 * 加载配置文件(my文件)，返回Map配置信息，默认编码UTF-8
	 * @param configFile 配置文件名，项目中classes下的配置文件，可以包含子目录。<b>（不能以“/”开头）</b>
	 * @return
	 */
	public static Map<String, Object> loadMy(String configFile){
		return loadMy(configFile, Charsets.UTF_8);
	}
	
	/**
	 * 加载配置文件(my文件)，返回Map配置信息
	 * @param configFile 配置文件名，项目中classes下的配置文件，可以包含子目录。<b>（不能以“/”开头）</b>
	 * @param charset 编码，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @return
	 */
	public static Map<String, Object> loadMy(String configFile, Charset charset){
		try(InputStream is = getInputStream(configFile)){
			return loadMy(is, charset);
		} catch (IOException e) {
			throw new RuntimeException("配置文件解析异常", e);
		}
	}
	
	/**
	 * 加载配置文件(my文件)，返回Map配置信息，默认编码UTF-8
	 * @param inputStream 配置文件流
	 * @return
	 */
	public static Map<String, Object> loadMy(InputStream inputStream){
		return loadMy(inputStream, Charsets.UTF_8);
	}
	
	/**
	 * 加载配置文件(my文件)，返回Map配置信息
	 * @param inputStream 配置文件流
	 * @param charset 编码，可使用常量 <i><b>Charsets.编码</b></i> 获取
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static Map<String, Object> loadMy(InputStream inputStream, Charset charset){
		//定义顶级容器
		LinkedHashMap<String, Object> ctnMap = new LinkedHashMap<>();
		//上级容器栈
		LinkedList<Object> lastCtn = new LinkedList<Object>();
		lastCtn.add(ctnMap);
		//记录当前[首行缩进，上次缩进，是否是子配置]
		Integer[] curr = {null, 0, 0};
		//读取文件
		FileUtils.read(inputStream, charset, (line, content)->{
			//如果不是注释，则解析
			if(!content.matches("^\\t*#.*")) {
				//当前缩进
				int indent = Regexp.getMatchOne("^\t*", content).length();
				//去掉缩进后的文本
				String text = content.substring(indent);
				//去掉空格后，如果不为空，则继续解析
				if(!text.trim().isEmpty()) {
					//首行缩进
					if(curr[0]==null) curr[0] = indent;
					//当前相对缩进
					int reladiff = indent - curr[0], diff = reladiff-curr[1];
					//分割符
					int sep = text.indexOf(':');
					//没有分割符
					if(sep==-1) {
						throw new RuntimeException("配置格式错误（第" + line + "行）：" + content);
					}
					//多次缩进
					if(diff>1 || (diff==1 && curr[2]!=1)) {
						throw new RuntimeException("配置缩进错误（第" + line + "行）：" + content);
					}//没有缩进，但又应该是缩进，则移除上次添加的子级Map
					else if(diff==0 && curr[2]==1) {
						lastCtn.removeLast();
					}//回退缩进
					else if(diff<0) {
						while(diff<0) {
							//层级上移
							lastCtn.removeLast();
							//回退一级
							diff++;
						}
					}
					//将当次缩进保存为上次缩进
					curr[1] = reladiff;
					//键和值
					String key = text.substring(0, sep), value = text.substring(sep+1);
					//如果值以空格开始，则为普通值
					if(value.startsWith(" ")) {
						//格式化值
						value = value.substring(1);
						//获取配置值
						Object val = value.startsWith("[") ? new LinkedList<Object>(Arrays.asList(value.substring(1, value.length()-1).split(", "))) : value;
						//判断key为加号，直接加入数组
						if("+".equals(key)) {
							//上级容器
							Object last = lastCtn.getLast();
							//如果是集合，直接保存
							if(last instanceof LinkedList) {
								addMyArray(last, val, line, content);
							}//LinkedHashMap
							else if(last instanceof LinkedHashMap) {
								//转换类型
								LinkedHashMap<String, Object> lastTemp = (LinkedHashMap<String, Object>) last;
								//如果是空的，则更新内容
								if(lastTemp.isEmpty()) {
									//移除最后一项
									lastCtn.removeLast();
									//上级容器
									Object obj = lastCtn.getLast();
									//上级如果是List集合
									if(obj instanceof LinkedList) {
										//上级容器
										LinkedList<Object> lastLvl = (LinkedList<Object>) obj;
										//移除最后一项
										lastLvl.pollLast();
										//重新添加最后一项
										lastLvl.add(new LinkedList<Object>());
										lastCtn.add(lastLvl.getLast());
									}//Map集合
									else {
										//最后一项的最后一个键值对
										Entry<String, Object> lastLvl = removeLast(obj);
										//新集合容器
										LinkedList<Object> list = new LinkedList<Object>();
										//保存
										putMyMap(obj, lastLvl.getKey(), list, line, content);
										lastCtn.add(list);
									}
									//保存集合
									addMyArray(lastCtn.getLast(), val, line, content);
								}//不是空的，抛出异常
								else {
									throw new RuntimeException("配置格式错误（第" + line + "行）：" + content + " ==> 数组项前面不能包含属性配置");
								}
							}
						}else {
							//保存
							putMyMap(lastCtn.getLast(), key, val, line, content);
						}
						//下次不是子级配置项
						curr[2] = 0;
					}
					//子项配置
					else {
						//如果是加号，则为集合项，需要把上层容器Map转为List
						if("+".equals(key)) {
							//上级容器
							Object l = lastCtn.getLast();
							//Map类型
							if(l instanceof LinkedHashMap) {
								//已有配置
								if(!((Map<?, ?>) l).isEmpty()) {
									throw new RuntimeException("配置格式错误（第" + line + "行）：" + content + " ==> 数组项前面不能包含属性配置");
								}
								//移除最后一项
								lastCtn.removeLast();
								//获取上级Map
								Object obj = lastCtn.getLast();
								//上级如果是List集合
								if(obj instanceof LinkedList) {
									//上级容器
									LinkedList<Object> last = (LinkedList<Object>) lastCtn.getLast();
									//移除最后一项
									last.pollLast();
									//重新添加最后一项
									last.add(new LinkedList<Object>());
									lastCtn.add(last.getLast());
								}//Map集合
								else {
									//最后一项的最后一个键值对
									Entry<String, Object> last = removeLast(obj);
									//新集合容器
									LinkedList<Object> list = new LinkedList<Object>();
									//保存
									putMyMap(obj, last.getKey(), list, line, content);
									lastCtn.add(list);
								}
							}
							//默认元素对象
							LinkedHashMap<String, Object> element = new LinkedHashMap<>();
							//添加元素
							addMyArray(lastCtn.getLast(), element, line, content);
							lastCtn.add(element);
						}//非集合
						else {
							//子级容器
							LinkedHashMap<String, Object> child = new LinkedHashMap<>();
							//保存子级容器
							putMyMap(lastCtn.getLast(), key, child, line, content);
							lastCtn.add(child);
						}
						//下次应该是子级配置项
						curr[2] = 1;
					}
				}
			}
		});
		//返回顶级容器
		return ctnMap;
	}
	
	/**
	 * 移除并返回集合的最后一项
	 * @param linkedHashMap 指定集合
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private static Entry<String, Object> removeLast(Object linkedHashMapObject) {
		LinkedHashMap<String, Object> linkedHashMap = (LinkedHashMap<String, Object>) linkedHashMapObject;
		//最后一项
		Entry<String, Object> last = null;
		//遍历获取最后一项
		for(Entry<String, Object> kv: linkedHashMap.entrySet()) {
			last = kv;
		}
		//移除最后一项
		linkedHashMap.remove(last.getKey());
		//返回最后一项
		return last;
	}
	
	/**
	 * 保存.my对象配置
	 * @param ctn 配置容器
	 * @param key 对象属性
	 * @param value 对象属性值
	 * @param line 当前行号
	 * @param content 当前行内容
	 */
	@SuppressWarnings("unchecked")
	private static void putMyMap(Object ctn, String key, Object value, Integer line, String content) {
		//是LinkedHashMap集合
		if(ctn instanceof LinkedHashMap) {
			((LinkedHashMap<String, Object>) ctn).put(key, value);return;
		}
		throw new RuntimeException("配置格式错误（第" + line + "行）：" + content + " ==> 数组项后面不能包含属性配置");
	}
	
	/**
	 * 保存.my集合配置
	 * @param ctn 配置容器
	 * @param value 集合元素值
	 * @param line 当前行号
	 * @param content 当前行内容
	 */
	@SuppressWarnings("unchecked")
	private static void addMyArray(Object ctn, Object value, Integer line, String content) {
		//是ArrayList集合
		if(ctn instanceof LinkedList) {
			((LinkedList<Object>) ctn).add(value);return;
		}
	}
	
	/**
	 * 获取输入流
	 * @param configFile
	 * @return
	 */
	private static InputStream getInputStream(String configFile) {
		//加载配置文件
		InputStream is = ConfigureUtils.class.getClassLoader().getResourceAsStream(configFile);
		//为空
		if(is==null) {
			//抛出异常
			throw new NullPointerException("未找到配置文件" + configFile);
		}
		//返回流
		return is;
	}
}
